package org.windowkit.toolkit.print;

import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;

import javax.print.event.PrintJobAdapter;
import javax.print.event.PrintJobEvent;
import javax.swing.JInternalFrame;
import javax.swing.SwingUtilities;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

import org.windowkit.toolkit.frame.GlassPane;


/**
 * This class allows a component to be printed.
 *
 * @author Eric Lundin
 */
//TODO broked
public class PrintUtility {

	/** Used to print a title along the top of the page. */
	String title;

	/**
	 * This object will perform the actual printing.
	 */
	AbstractPrinter printer;

	/**
	 * This is the print job we will use to allow configuration of printing
	 * properties by the user.
	 */
	private PrinterJob printJob;

	/**
	 * Flag tracks whether to display the print preview window or go right to
	 * the print dialog.
	 */
	private boolean printPreview;

	/**
	 * The high level component to print.
	 */
	private PrintableComponent component;

	/**
	 * The print preview window.
	 */
	private PrintPreviewWindow preview;

	private PreviewFrameListener previewFrameListener;

	private ParentGlassListener parentGlassListener;

	private ParentFrameListener parentFrameListener;

	private JInternalFrame parentFrame;

	private GlassPane tempParentGlassPane;

	/**
	 * @param component
	 *            The component to print.
	 * @param header
	 *            The header to display above the component. In order for this
	 *            to be effective the header must be as wide as the component.
	 *            This is mostly for tables which have columns whose total width
	 *            is equal to its header.
	 */
	public PrintUtility(final PrintableComponent component, final String windowTitle,
			final boolean printPreview) {
		super();
		init(component, windowTitle, printPreview);
	}

	/**
	 * @param component
	 *            The component to print.
	 */
	public PrintUtility(final PrintableComponent component, final boolean printPreview) {
		super();
		init(component, "", printPreview);
	}

	/**
	 * Initialize the fields.
	 *
	 * @param component
	 *            The component to print.
	 * @param header
	 *            The header to display above the component. In order for this
	 *            to be effective the header must be as wide as the component.
	 *            This is mostly for tables which have columns whose total width
	 *            is equal to its header.
	 * @param title
	 *            Allows a string of text to be displayed along the top of the
	 *            page.
	 */
	private void init(final PrintableComponent comp, final String title,
			final boolean printPreview) {
		if (comp == null) {
			throw new NullPointerException("Component to print cannot be null");
		}

		this.component = comp;
		this.printPreview = printPreview;
		this.title = title;
		printer = new DefaultPrinter(component.getPrintComponent(), component
				.getPrintHeader(), title);
	}

	/**
	 * Print the entire component
	 *
	 * @throws PrinterException
	 *             Forwards PrinterException to calling component as that
	 *             component will be better able to alert the user to the
	 *             problem which occured.
	 */
	public void print() throws PrinterException {
		printJob = PrinterJob.getPrinterJob();
		printJob.setPrintable(printer);
		component.prepareForPrint();
		if (printPreview) {
			launchPreviewWindow();
		} else {
			if (printJob.printDialog()) {
				printJob.print();
			}
			component.restorePrePrintState();
		}
	}

	/**
	 * Launches the print preview window.
	 */
	private void launchPreviewWindow() {
		preview = new PrintPreviewWindow(title, printJob, printer);
		parentFrame = (JInternalFrame) SwingUtilities.getAncestorOfClass(
				JInternalFrame.class, (Component) component);

		// Until 1.6 is released and installed allowing modal frames setup this
		// hack fix to prevent changes
		// to table from invalidating preview with selection lines etc.
		if (parentFrame != null) {
			previewFrameListener = new PreviewFrameListener(parentFrame);
			parentGlassListener = new ParentGlassListener(parentFrame);
			parentFrameListener = new ParentFrameListener();
//			preview.addInternalFrameListener(previewFrameListener);
			tempParentGlassPane = new GlassPane();
			tempParentGlassPane.addMouseListener(parentGlassListener);
			parentFrame.addInternalFrameListener(parentFrameListener);
			parentFrame.setGlassPane(tempParentGlassPane);
			tempParentGlassPane.setVisible(true);
			parentFrame.setFocusable(false);
		}
//		Desktop.getDesktopPane().addWindow(preview);
	}

	/**
	 * Cleanup references
	 */
	public void cleanUp() {
//		preview.removeInternalFrameListener(previewFrameListener);
		tempParentGlassPane.removeMouseListener(parentGlassListener);
		parentFrame.removeInternalFrameListener(parentFrameListener);
		printer = null;
		preview = null;
		printer = null;
		printJob = null;
		component = null;
		tempParentGlassPane = null;
	}

	public JInternalFrame getParentOfComp() {
		return (JInternalFrame) SwingUtilities.getAncestorOfClass(
				JInternalFrame.class, (Component) component);
	}

	/**
	 * Listens for the preview frame to close in order to set the glass pane
	 * back to normal on the frame containing the previewed component.
	 *
	 * @author Eric Lundin
	 */
	protected class PreviewFrameListener extends InternalFrameAdapter {
		private JInternalFrame parentFrame;

		private Component parentGlassPane;

		/**
		 * @param parentFrame
		 *            The frame that holds the component to be printed.
		 * @param parentGlassPane
		 *            The original glassPane this frame used.
		 */
		public PreviewFrameListener(final JInternalFrame parentFrame) {
			this.parentFrame = parentFrame;
			this.parentGlassPane = parentFrame.getGlassPane();
		}

		@Override
		public void internalFrameClosed(final InternalFrameEvent e) {
			if (e.getSource() == preview) {
				component.restorePrePrintState();
				parentFrame.getGlassPane().setVisible(false);
				parentFrame.setGlassPane(parentGlassPane);
				cleanUp();
				parentGlassPane = null;
				parentFrame = null;
			}
		}
	}

	/**
	 * Listens for the end of a print job. Calls restorePrePrintState on the
	 * printed component once the job is complete.
	 *
	 * @author Eric Lundin
	 */
	protected class PrintFinishedListener extends PrintJobAdapter {
		@Override
		@SuppressWarnings("unused")
		public void printJobCompleted(final PrintJobEvent pje) {
			component.restorePrePrintState();
		}

		@Override
		@SuppressWarnings("unused")
		public void printJobCanceled(final PrintJobEvent pje) {
			component.restorePrePrintState();
		}

		@Override
		@SuppressWarnings("unused")
		public void printJobFailed(final PrintJobEvent pje) {
			component.restorePrePrintState();
		}
	}

	/**
	 * This class listens for the glass pane on the launching window to be
	 * clicked and displays a message letting them know they must close the
	 * preview window before modifying the table.
	 *
	 * @author Eric Lundin
	 */
	protected class ParentGlassListener extends MouseAdapter {
		private final JInternalFrame parent;

		public ParentGlassListener(final JInternalFrame parent) {
			this.parent = parent;
		}

		@Override
		@SuppressWarnings("unused")
		public void mouseClicked(final MouseEvent e) {

		}
	}

	/**
	 * Listens for the parent to become activated so we can set the glass pane
	 * as visible again.
	 *
	 * @author Eric Lundin
	 */
	protected class ParentFrameListener extends InternalFrameAdapter {
		/*
		 * (non-Javadoc)
		 *
		 * @see javax.swing.event.InternalFrameListener#internalFrameActivated(javax.swing.event.InternalFrameEvent)
		 */
		@Override
		public void internalFrameActivated(final InternalFrameEvent e) {
			((JInternalFrame) e.getSource()).getGlassPane().setVisible(true);
		}
	}

}
